#include "convex_hull_iterator.h"
#include "template_functions.h"

#include <boost\numeric\interval.hpp>

#include <gmpxx.h>

void convex_hull_iterator::init_intervals(const std::vector<point>::iterator& it, boost::numeric::interval<double>& interval1, boost::numeric::interval<double>& interval2, void (*coord)(const point&, const point&, boost::numeric::interval<double>&))
{
    coord(*it, vect, interval2);

    if (it == first_vertex)
    {
        coord(*convex_hull, vect, interval1);
    } else
    {
        coord(*(it - 1), vect, interval1);
    }
};

void convex_hull_iterator::init_mpqs(const std::vector<point>::iterator& it, mpq_class& mpq1, mpq_class& mpq2, void (*coord)(const point&, const point&, mpq_class&))
{
    coord(*it, vect, mpq2);

    if (it == first_vertex)
    {
        coord(*convex_hull, vect, mpq1);
    } else
    {
        coord(*(it - 1), vect, mpq1);
    }
};

bool convex_hull_iterator::can_go_left(const std::vector<point>::iterator& it)
{
    boost::numeric::interval<double> x1, x2;

    init_intervals(it, x1, x2, x_coord);

    if (x1.lower() > x2.upper())
    {
        return false;
    }

    if (x2.lower() > x1.upper())
    {
        return true;
    }

    mpq_class mpq_x1, mpq_x2;

    init_mpqs(it, mpq_x1, mpq_x2, x_coord);

    return mpq_x1 <= mpq_x2;
};

        
bool convex_hull_iterator::can_go_right(const std::vector<point>::iterator& it)
{
    boost::numeric::interval<double> x1, x2;

    init_intervals(it, x1, x2, x_coord);

    if (x1.lower() > x2.upper())
    {
        return true;
    }

    if (x2.lower() > x1.upper())
    {
        return false;
    }

    mpq_class mpq_x1, mpq_x2;

    init_mpqs(it, mpq_x1, mpq_x2, x_coord);

    return mpq_x1 >= mpq_x2;
};

bool convex_hull_iterator::can_go_up(const std::vector<point>::iterator& it)
{
    boost::numeric::interval<double> y1, y2;

    init_intervals(it, y1, y2, y_coord);

    if (y1.lower() > y2.upper())
    {
        return true;
    }

    if (y2.lower() > y1.upper())
    {
        return false;
    }

    mpq_class mpq_y1, mpq_y2;

    init_mpqs(it, mpq_y1, mpq_y2, y_coord);

    return mpq_y1 >= mpq_y2;
};

convex_hull_iterator::convex_hull_iterator(const std::reverse_iterator<std::vector<point>::iterator>& it)
{
    current = it;
}

convex_hull_iterator::convex_hull_iterator(const std::reverse_iterator<std::vector<point>::iterator>& it, const std::vector<point>::iterator& convex_hull_it, const std::vector<point>::iterator& first_vertex_it, const std::reverse_iterator<std::vector<point>::iterator>& first_vertex_reverse_it)
{
    current = it;
    leftmost_it = (uppermost_it = (rightmost_it = (convex_hull = convex_hull_it)));
    first_vertex = first_vertex_it;
    first_vertex_ri = first_vertex_reverse_it;

    area = std::numeric_limits<double>::max();
}

const double convex_hull_iterator::operator*() const
{
    return area;
}

convex_hull_iterator& convex_hull_iterator::operator++()
{
    if (current == first_vertex_ri)
    {
        vect.x = current->x - convex_hull->x;
        vect.y = current->y - convex_hull->y;
    } else
    {
        vect.x = current->x - (current - 1)->x;
        vect.y = current->y - (current - 1)->y;
    }

    while (can_go_right(rightmost_it))
    {
        if (rightmost_it == first_vertex)
        {
            rightmost_it = convex_hull;
        } else
        {
            rightmost_it--;
        }
    }

    if (current.base() == convex_hull)
    {
        leftmost_it = rightmost_it;
    }
    
    while (can_go_left(leftmost_it))
    {
        if (leftmost_it == first_vertex)
        {
            leftmost_it = convex_hull;
        } else
        {
            leftmost_it--;
        }
    }

    while (can_go_up(uppermost_it))
    {
        if (uppermost_it == first_vertex)
        {
                uppermost_it = convex_hull;
        } else
        {
            uppermost_it--;
        }
    }

    double r = hypot(vect.x, vect.y);

    double tmp1, tmp2;
    x_coord(*rightmost_it, vect, tmp1); x_coord(*leftmost_it, vect, tmp2);

    double delta_x = (tmp1 - tmp2) / r;

    y_coord(*uppermost_it, vect, tmp1); y_coord(*current, vect, tmp2);
    double delta_y = (tmp1 - tmp2) / r;

    area = delta_x * delta_y;

    current++;

    return *this;
}

bool convex_hull_iterator::operator!=(const convex_hull_iterator& chi) const
{
    return chi.current != current;
}

bool convex_hull_iterator::operator==(const convex_hull_iterator& chi) const
{
    return !(*this != chi);
}